﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Design;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace HRM.Migrator
{
    class Program
    {
        static string ContextAssembly = "HRM.Migrator";
        static string SnapshotName = "";
        static void Main(string[] args)
        {
            Console.WriteLine("Please enter the migration name.");
            SnapshotName = Console.ReadLine();
            try
            {
                //创建数据库
                CreateDb("HRMDb");
                //执行迁移
                RunUpdateAsync();
                StartMigrations();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        private static void CreateDb(string DbName)
        {
            StringBuilder str = new StringBuilder();
            SqlConnection myConn = new SqlConnection("Server=.;Integrated security=SSPI;database=master");
            str.AppendFormat($"if not exists (select 1 from sys.databases where name = '{DbName}')  ");
            str.AppendFormat($"CREATE DATABASE {DbName} ON PRIMARY(NAME = {DbName}_Data, ");
            str.AppendFormat($"FILENAME = N'{ConfigurationHelper.Get("DbFilePath")}{DbName}.mdf', ");
            str.AppendFormat("SIZE = 5MB, FILEGROWTH = 1MB) ");
            str.AppendFormat($"LOG ON (NAME = {DbName}_Log, ");
            str.AppendFormat($"FILENAME = N'{ConfigurationHelper.Get("DbFilePath")}{DbName}_log.ldf', ");
            str.AppendFormat("SIZE = 2MB, ");
            str.AppendFormat("FILEGROWTH = 10%)");

            SqlCommand myCommand = new SqlCommand(str.ToString(), myConn);
            try
            {
                myConn.Open();
                myCommand.ExecuteNonQuery();
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                if (myConn.State == ConnectionState.Open)
                {
                    myConn.Close();
                }
            }
        }

        private static void RunUpdateAsync()
        {
            var OptionsBuilder = new DbContextOptionsBuilder<HRMDbContext>();
            OptionsBuilder.UseSqlServer(ConfigurationHelper.Get("MSSQLConnection"), builder =>
            {
                builder.EnableRetryOnFailure(
                    maxRetryCount: 5,
                    maxRetryDelay: TimeSpan.FromSeconds(30),
                    errorNumbersToAdd: null);
            });
            using (var _dbContext = new HRMDbContext(OptionsBuilder.Options))
            {
                //_dbContext.Database.MigrateAsync();//根据Migrations修改/创建数据库

                IModel lastModel = null;
                try
                {
                    // 这里用一个表来存迁移记录，迁移时将上一个版本取出来
                    var lastMigration = _dbContext.Set<_EFMigrationsHistory1>()
                        .OrderByDescending(e => e.MigrationTime)
                        .OrderByDescending(e => e.Id) // mysql下自动生成的时间日期字段时间精度为秒
                        .FirstOrDefault();
                    // 上一个版本生成的类型文本是以base64编码存储的，取出来还原成DbContext模型对象
                    lastModel = lastMigration == null ? null : (CreateModelSnapshot(Encoding.UTF8.GetString(Convert.FromBase64String(lastMigration.SnapshotDefine)), lastMigration.ProductVersion).Result?.Model);
                }
                catch (DbException ex) { Console.WriteLine(ex.ToString()); }

                // 需要从历史版本库中取出快照定义，反序列化成类型 GetDifferences(快照模型, context.Model);
                // 实际情况下要传入历史快照
                var modelDiffer = _dbContext
                    .GetInfrastructure()
                    .GetService<IMigrationsModelDiffer>();
                var hasDiffer = modelDiffer.HasDifferences(lastModel, _dbContext.Model);

                if (hasDiffer)
                {
                    // 用 IMigrationsModelDiffer 的 GetDifferences 方法获取迁移的操作对象
                    var upOperations = modelDiffer.GetDifferences(lastModel, _dbContext.Model);

                    using (var trans = _dbContext.Database.BeginTransaction())
                    {
                        try
                        {
                            // 通过 IMigrationsSqlGenerator 将操作迁移操作对象生成迁移的sql脚本，并执行
                            _dbContext.GetInfrastructure()
                                .GetRequiredService<IMigrationsSqlGenerator>()
                                .Generate(upOperations, _dbContext.Model)
                                .ToList()
                                .ForEach(cmd => _dbContext.Database.ExecuteSqlCommand(cmd.CommandText));

                            _dbContext.Database.CommitTransaction();
                        }
                        catch (DbException ex)
                        {
                            _dbContext.Database.RollbackTransaction();
                            throw ex;
                        }

                        // 生成新的快照，存起来
                        var snapshotCode = new DesignTimeServicesBuilder(typeof(HRMDbContext).Assembly, typeof(HRMDbContext).Assembly, new ModuleDbOperationReporter(), new string[0])
                            .Build((DbContext)_dbContext)
                            .GetService<IMigrationsCodeGenerator>()
                            .GenerateSnapshot(ContextAssembly, typeof(HRMDbContext), SnapshotName, _dbContext.Model);

                        _dbContext.Set<_EFMigrationsHistory1>().Add(new _EFMigrationsHistory1()
                        {
                            SnapshotDefine = Convert.ToBase64String(Encoding.UTF8.GetBytes(snapshotCode)),
                            MigrationTime = DateTime.Now,
                            ProductVersion = SnapshotName
                        });

                        _dbContext.SaveChanges();
                    }
                }
            }
        }



        private static Task<ModelSnapshot> CreateModelSnapshot(string codedefine, string LastProductVersion)
        {
            // 生成快照，需要存到数据库中供更新版本用
            var references = typeof(HRMDbContext).Assembly
                .GetReferencedAssemblies()
                .Select(e => MetadataReference.CreateFromFile(Assembly.Load(e).Location))
                .Union(new MetadataReference[]
                {
                    MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location),
                    MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
                    MetadataReference.CreateFromFile(typeof(Object).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof(HRMDbContext).Assembly.Location)
                });

            var compilation = CSharpCompilation.Create(ContextAssembly)
                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                .AddReferences(references)
                .AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(codedefine));

            return Task.Run(() =>
            {
                using (var stream = new MemoryStream())
                {
                    var compileResult = compilation.Emit(stream);
                    return compileResult.Success
                        ? Assembly.Load(stream.GetBuffer()).CreateInstance(ContextAssembly + "._" + LastProductVersion.Replace(".", "")) as ModelSnapshot
                        : null;
                }
            });
        }


        private static void StartMigrations()
        {
            Console.WriteLine("Entity Framework Core Migrate Start !");
            Console.WriteLine("Get Pending Migrations...");
            var builder = new DbContextOptionsBuilder<HRMDbContext>();
            builder.UseSqlServer(ConfigurationHelper.Get("MSSQLConnection"));
            using (var db = new HRMDbContext(builder.Options))
            {
                //获取所有待迁移
                Console.WriteLine($"Pending Migrations：\n{string.Join('\n', db.Database.GetPendingMigrations().ToArray())}");

                Console.WriteLine("Do you want to continue?(Y/N)");

                if (Console.ReadLine().Trim().ToLower() == "n")
                {
                    return;
                }

                Console.WriteLine("Migrating...");

                try
                {

                    //执行迁移
                    db.Database.Migrate();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
            }

            Console.WriteLine("Entity Framework Core Migrate Complete !");
            Console.WriteLine("Press any key to exit !");
            Console.ReadKey();
        }
    }
}
